import ctypes
import os, sys
sys.path.append(
    os.path.abspath(
        os.path.join(
            os.path.dirname(__file__), "../../")))
from lib3ddevil1.bindings import libc
del os, sys

#--------------------------------------+
#       Basic Struct
#--------------------------------------+

class Header(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("numMesh",        ctypes.c_ubyte),
        ("unknownNumberB", ctypes.c_ubyte),
        ("unknownNumberC", ctypes.c_ubyte),
        ("unknownNumberD", ctypes.c_ubyte),
        ("padding",        ctypes.c_int),
        ("unknownOffset",  ctypes.c_ulonglong)
    ]

class MeshHeader(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("numBatch",        ctypes.c_short),
        ("numVertex",       ctypes.c_short),
        ("u",               ctypes.c_uint),
        ("offsetBatches",   ctypes.c_ulonglong),
        ("flags",           ctypes.c_ulonglong)
    ]

class Coordinate(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("x", ctypes.c_float),
        ("y", ctypes.c_float),
        ("z", ctypes.c_float)
    ]

    def __str__(self):
        return "(%s, %s, %s)" % (str(self.x), str(self.y), str(self.z))

class UVs(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("u", ctypes.c_short),
        ("v", ctypes.c_short)
    ]

    def __str__(self):
        return "(%s, %s)" % (str(self.u), str(self.v))

class BoneIndexes(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("indexes", ctypes.c_ubyte * 4),
    ]

class BoneWeights(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("weights", ctypes.c_short)
    ]


class BatchData(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("numVertex",           ctypes.c_short),
        ("uB",                  ctypes.c_short),
        ("padding",             ctypes.c_uint),
        ("offsetPositions",     ctypes.c_ulonglong),
        ("offsetNormals",       ctypes.c_ulonglong),
        ("offsetUVs",           ctypes.c_ulonglong),
        ("offsetBoneIndexes",   ctypes.c_ulonglong),
        ("offsetBoneWeights",   ctypes.c_ulonglong),
        ("offsets",             ctypes.c_ulonglong)
    ]

class VertexData(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("positions",   ctypes.POINTER(Coordinate)),
        ("normals",     ctypes.POINTER(Coordinate)),
        ("u",           ctypes.POINTER(UVs)),
        ("bi",          ctypes.POINTER(BoneIndexes)),
        ("bw",          ctypes.POINTER(BoneWeights))
    ]

class Batch(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("bd", ctypes.POINTER(BatchData)),
        ("vd", VertexData)
    ]

class Mesh(ctypes.Structure):
    _pack_   = 1
    _fields_ = [
        ("b", ctypes.POINTER(Batch))
    ]

class Devil1GEO_FN(ctypes.Structure):
    _fields_ = [
        ("printheader",     ctypes.CFUNCTYPE(
                                     None,
                                     ctypes.POINTER(Header))),
        ("printmeshheader", ctypes.CFUNCTYPE(
                                     None,
                                     ctypes.POINTER(MeshHeader))),
        ("printbatch",      ctypes.CFUNCTYPE(
                                     None,
                                     ctypes.POINTER(Batch))),
        ("printcoordinate", ctypes.CFUNCTYPE(
                                     None,
                                     ctypes.POINTER(Coordinate))),
        ("getheader",       ctypes.CFUNCTYPE(
                                     ctypes.c_bool,
                                     ctypes.POINTER(ctypes.POINTER(Header)),
                                     ctypes.c_char_p)),
        ("getmeshheader",   ctypes.CFUNCTYPE(
                                     ctypes.c_bool,
                                     ctypes.POINTER(ctypes.POINTER(MeshHeader)),
                                     ctypes.c_uint,
                                     ctypes.c_char_p)),
        ("getbatch",        ctypes.CFUNCTYPE(
                                     ctypes.c_bool,
                                     ctypes.POINTER(Batch),
                                     ctypes.c_uint,
                                     ctypes.c_char_p)),
        ("getmesh",         ctypes.CFUNCTYPE(
                                     ctypes.c_bool,
                                     ctypes.POINTER(Mesh),
                                     ctypes.c_uint,
                                     ctypes.c_char_p,
                                     ctypes.c_uint))
    ]

devil1geo = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO")
del libc

#--------------------------------------+
#       Pythonic Object
#--------------------------------------+

class pyGeoHeader:
    def __init__(self, filedata):
        self.cstruct = ctypes.pointer(Header())
        ptrofptr = ctypes.byref(self.cstruct)
        if filedata:
            if not devil1geo.getheader(ptrofptr, filedata):
                raise RuntimeError("failed to get geometry file header")
        return

    def show(self):
        devil1geo.printheader(self.cstruct)

    def getnummesh(self):
        return self.cstruct.contents.numMesh

    def getunknownb(self):
        return self.cstruct.contents.unknownNumberB

    def getunknownc(self):
        return self.cstruct.contents.unknownNumberC
 
    def getunknownd(self):
        return self.cstruct.contents.unknownNumberD

    def getpadding(self):
        return hex(self.cstruct.contents.padding)

    def getunknownoffset(self):
        return hex(self.cstruct.contents.unknownOffset)


class pyMeshHeader:
    def __init__(self, i, filedata):
        self.cstruct = ctypes.pointer(MeshHeader())
        ptrofptr = ctypes.byref(self.cstruct)
        if filedata:
            if not devil1geo.getmeshheader(ptrofptr, i, filedata):
                raise RuntimeError("failed to get mesh header #" + str(i))
        return
    pass

    def show(self):
        devil1geo.printmeshheader(self.cstruct)

    def getnumbatch(self):
        return self.cstruct.contents.numBatch

    def getnumvertex(self):
        return self.cstruct.contents.numVertex

    def getunknown(self):
        return hex(self.cstruct.contents.u)

    def getoffsetbatches(self):
        return self.cstruct.contents.offsetBatches

    def getflags(self):
        return self.cstruct.contents.flags

class pyMesh:
    def __init__(self, i, filedata):
        self.cstruct = Mesh()
        if filedata:
            mh = pyMeshHeader(i, filedata)
            # allocate memory for the size of batch * number of batches
            memsize = ctypes.sizeof(Batch) * mh.getnumbatch()
            self.cstruct.b = ctypes.cast(
                             ctypes.create_string_buffer(memsize), 
                             ctypes.POINTER(Batch))
            if not devil1geo.getmesh(
                              ctypes.byref(self.cstruct), 
                              i, 
                              filedata, 
                              len(filedata)):
                raise RuntimeError("failed to get mesh")
            del mh, memsize
        return

    def show(self):
        if self.cstruct.b:
            devil1geo.printbatch(self.cstruct.b)
        else:
            print("nothing to show")

    def getbatchdata(self):
        return self.cstruct.b.contents.bd.contents

    def getpositions(self):
        length = self.cstruct.b.contents.bd.contents.numVertex
        return self.cstruct.b.contents.vd.positions[:length]

    def getnormals(self):
        length = self.cstruct.b.contents.bd.contents.numVertex
        return self.cstruct.b.contents.vd.normals[:length]

    def getuvs(self):
        length = self.cstruct.b.contents.bd.contents.numVertex
        return self.cstruct.b.contents.vd.u[:length]

    def getboneindexes(self):
        length = self.cstruct.b.contents.bd.contents.numVertex
        return self.cstruct.b.contents.vd.bi[:length]

    def getboneweights(self):
        length = self.cstruct.b.contents.bd.contents.numVertex
        return self.cstruct.b.contents.vd.bw[:length]
